package tool.classes.exam;

import tool.interfaces.exam.OnlineItf;
import tool.property.exam.OnlineProperty;

import java.io.*;
import java.util.ArrayList;
import java.util.Properties;

public class Online implements OnlineItf {
    private String path;
    private String srcPath;
    private String fType;
    private String ans;

    private boolean oneOrMore;  // 判断是否有传入参值 没有为 false
    private String onlyOut = null;
    private ArrayList<String> ins = null;
    private ArrayList<String> outs = null;


    /**
     * 初始化生成文件
     */
    public Online(String type, String ans, OnlineProperty onlineExam) {

        try {
            // 配置对象
            Properties properties = new Properties();

            // 类加载器，读取 src 下的 jdbc.properties
            InputStream is = Online.class.getClassLoader().getResourceAsStream("jdbc.properties");

            // 导入
            properties.load(is);

            this.path = properties.getProperty("examPath");

            switch (type) {
                case "c" -> this.srcPath = this.path.concat("/p.").concat("c");
                case "py3", "py2" -> this.srcPath = this.path.concat("/p.").concat("py");
                case "java" -> this.srcPath = this.path.concat("/p.").concat("java");
                case "go" -> this.srcPath = this.path.concat("/p.").concat("go");
            }
            this.fType = type;
            this.ans = ans;

        } catch (IOException e) {
            e.printStackTrace();
        }

        getInsOuts(onlineExam);

    }

    /**
     * 创建文件，写入代码
     */
    @Override
    public void makeFile() {

        try {
            File f = new File(this.srcPath);
            if (!f.exists()) {
                f.createNewFile();
            }
            BufferedWriter bw = new BufferedWriter(new FileWriter(f.getAbsolutePath()));
            bw.write(ans);
            bw.flush();
            bw.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * 在终端运行，需要编译类型如c， java第一行代码用于编译
     *
     * @param inputting 测试用数据，执行时传参，编译时传 null，无参运行为 null
     */
    private String readDataFromShell(Process exec, String inputting, String outing) throws InterruptedException, IOException {
        InputStreamReader isr = null;

        OutputStreamWriter osw = new OutputStreamWriter(exec.getOutputStream());

        if (this.oneOrMore && inputting != null) {
            BufferedWriter bw = new BufferedWriter(osw);
            bw.write(inputting);
            bw.newLine();
            bw.write("\n");
            bw.flush();
            bw.close();
        }

        Thread.sleep(2000);
        try {
            if (exec.exitValue() == 0) {
                isr = new InputStreamReader(exec.getInputStream());
            } else {
                isr = new InputStreamReader(exec.getErrorStream());
            }
            BufferedReader br = new BufferedReader(isr);

            StringBuilder sb = new StringBuilder();
            String line;
            while ((line = br.readLine()) != null) {
                sb.append(line);
                sb.append("\n");
            }
            br.close();

            exec.destroy();

            if (outing != null) {
                if (sb.toString().trim().equals(outing.trim())) {

                    return "answers true";
                }
            }

            return "answers false";
        } catch (Exception e) {
            return "timeout error";
        } finally {
            if (isr != null) {
                isr.close();
            }
        }
    }

    /**
     * 按代码类型调用不同终端命令
     */
    @Override
    public String run() throws IOException, InterruptedException {

        return switch (this.fType) {
            case "c" -> runC();
            case "py3" -> runPy3();
            case "java" -> runJava();
            case "go" -> runGo();
            default -> "type error";
        };

    }

    /**
     * Java 代码运行
     */
    private String runJava() throws IOException, InterruptedException {
        String cmd = this.path + "/javaShell.sh";
        return terminalRun(cmd, Runtime.getRuntime().exec(cmd));
    }

    /**
     * python3 代码运行
     */
    private String runPy3() throws IOException, InterruptedException {
        String cmd = this.path + "/python3Shell.sh";
        return terminalRun(cmd, Runtime.getRuntime().exec(cmd));
    }

    /**
     * go语言 代码运行
     */
    private String runGo() throws IOException, InterruptedException {
        String cmd = this.path + "/goShell.sh";
        return terminalRun(cmd, Runtime.getRuntime().exec(cmd));
    }

    /**
     * C语言 代码运行
     */
    private String runC() throws IOException, InterruptedException {
        String cmd = this.path + "/cShell.sh";
        return terminalRun(cmd, Runtime.getRuntime().exec(cmd));
    }

    /**
     * 执行
     */
    private String terminalRun(String cmd, Process exec) throws InterruptedException, IOException {

        String ans = null;
        if (this.oneOrMore) {
            for (int i = 0; i < this.ins.size(); i++) {
                ans = readDataFromShell(Runtime.getRuntime().exec(cmd), this.ins.get(i), this.outs.get(i));
                if (ans.equals("answers false")) {
                    return "answers false";
                }
            }
        } else {
            ans = readDataFromShell(exec, null, this.onlyOut);
        }

        return ans;
    }

    /**
     * 获取数据 并确定题目参数状态
     */
    private void getInsOuts(OnlineProperty onlineExam) {

        if (onlineExam.getParameter() == 0) {
            this.oneOrMore = false;
            this.onlyOut = onlineExam.getOut0();
            return;
        }

        this.oneOrMore = true;

        ArrayList<String> in = new ArrayList<>();
        ArrayList<String> out = new ArrayList<>();

        if (onlineExam.getIn1() != null && onlineExam.getOut1() != null) {
            in.add(onlineExam.getIn1());
            out.add(onlineExam.getOut1());
        }
        if (onlineExam.getIn2() != null && onlineExam.getOut2() != null) {
            in.add(onlineExam.getIn2());
            out.add(onlineExam.getOut2());
        }
        if (onlineExam.getIn3() != null && onlineExam.getOut3() != null) {
            in.add(onlineExam.getIn3());
            out.add(onlineExam.getOut3());
        }

        this.ins = in;
        this.outs = out;

    }
}
